(****************************************************)
(*													*)
(*	file:  CarWash.m								*)
(*													*)
(*	Car Wash simulation using SimulationToolbox.  	*)
(*													*)
(*	REFERENCE:  Goldberg and Robson, SMALLTALK-80:	*) 
(*	THE LANGUAGE AND ITS IMPLEMENTATION,			*) 
(*	pp. 518-521.									*)
(*													*)
(*	Written in SemperSoft Modula-2 v.1.1.2			*)
(*													*)
(*	Allen Stenger	May 1989  						*)
(*													*)
(****************************************************)

MODULE CarWash;

FROM SYSTEM				IMPORT ADDRESS,ADR;
FROM InOut				IMPORT ReadCard,Write,
								WriteCard,WriteLn,
								WriteString;
FROM Terminal			IMPORT WritePString;
FROM SimulationToolbox	IMPORT Duration,Requester,
								SimulationQueue;
FROM SimulationToolbox	IMPORT CreateNewThread,
								CurrentTime,HaltThread,
								HaltSimulation,Hold,
								InitializeQueue,
								PlaceOrder,Reactivate,
								Serve;
FROM Distributions		IMPORT ExponentialDistribution,
								UniformDistribution;

CONST
	WS				=	8192;	(* default work size *)

(*	Note:  
	Times (Duration type) are in 0.01-minute units. *)
	
	WashArrivalMean	
				=	2000;	(* mean arrival time for 
								wash only *)
	WashAndWaxArrivalMean	
				=	3000;	(* mean arrival time for 
								wash and wax *)
	MinWashTime	=	1200;	(* min and max wash times *)
	MaxWashTime	=	2600;
	MinWaxTime	=	800;	(* min and max wax times *)
	MaxWaxTime	=	1200;
	
TYPE
	DesiredService	=	( Wash, WashAndWax );
	WasherParams	=	RECORD 
						(* for starting washer thread *)
							WhoAmI	:	CARDINAL;
						END; (* RECORD *)
	CarParams		=	RECORD 
						(* for starting car thread *)
							WhoAmI	:	CARDINAL;
							type	:	DesiredService;
						END; (* RECORD *)
	ServiceParams	=	RECORD 
					(* describes car wanting service *)
							Car		:	CARDINAL;
							Service	:	DesiredService;
						END; (* RECORD *)
VAR
	latestCar		:	ARRAY 
							DesiredService OF CARDINAL;
					(* numbers of last cars created *)

	CarWashEntrance	:	SimulationQueue;
	timeLimit		:	Duration; (* extent of 												simulation *)
	numberOfWashers	:	CARDINAL; 
						(* number of servers *)
	

PROCEDURE LoggitWasher( washer : CARDINAL;
						s : ARRAY OF CHAR;
						type : DesiredService; 
						car : CARDINAL );
BEGIN
	WriteCard( CurrentTime(),6 );
	Write(" ");
	WriteString("Washer");	
	WriteCard( washer,3 );
	Write(" ");
	IF type = Wash
	THEN
		WriteString("(Wash");
	ELSE
		WriteString("(WashAndWax");
	END; (* IF *)
	WriteCard( car,3 );
	WriteString(") ");
	WriteString( s );
	WriteLn;
END LoggitWasher;

PROCEDURE LoggitCar( 	car : CARDINAL; 
						s : ARRAY OF CHAR; 
						type : DesiredService );
BEGIN
	WriteCard( CurrentTime(),6 );
	Write(" ");
	IF type = Wash
	THEN
		WriteString("Wash ");
	ELSE
		WriteString("WashAndWax ");
	END; (* IF *)
	WriteCard( car,3 );
	Write(" ");
	WriteString( s );
	WriteLn;
END LoggitCar;

PROCEDURE SimulateWasher( parameterAddress : ADDRESS );
VAR
	wpp		:	POINTER TO WasherParams;
	whoAmI	:	CARDINAL; (* number of this washer *)
	r		:	Requester; (* who is being served *)
	sType	:	POINTER TO ServiceParams; 
	service	:	DesiredService;
	car	:	CARDINAL;
BEGIN
	wpp := parameterAddress;
	whoAmI := wpp^.WhoAmI;
	LOOP
		Serve( CarWashEntrance, sType, r);
		WITH sType^ DO
			service := Service;
			car := Car;
		END; (* WITH *)
		
		(* wash car *)
		LoggitWasher( whoAmI,"Washing car.",
						service,car );
		Hold( UniformDistribution( 
				MinWashTime, MaxWashTime 
					) );
		LoggitWasher( whoAmI,"Wash complete.",
						service,car );
		IF service = WashAndWax
		THEN (* also wax car *)
			LoggitWasher( whoAmI,"Waxing car.",
							service,car );
			Hold( UniformDistribution( 
					MinWaxTime, MaxWaxTime ) );
			LoggitWasher( whoAmI,"Wax complete.",
							service,car );
		END; (* IF *)
		
		Reactivate( r );
		LoggitWasher( whoAmI,"Ready for next car.",
						service,car );
	END; (* LOOP *)
END SimulateWasher;

PROCEDURE CreateNewCar( type : DesiredService ); FORWARD;

PROCEDURE SimulateCar( parameterAddress : ADDRESS );
VAR
	cpp		:	POINTER TO CarParams;
	whoAmI	:	CARDINAL; (* number of this car *)
	type	:	DesiredService;
	mean	:	Duration; (* mean inter-arrival time *)
	carDesc	:	ServiceParams;
BEGIN
	cpp := parameterAddress;
	type := cpp^.type;
	whoAmI := cpp^.WhoAmI;
	IF whoAmI = 1 
	THEN (* start running right away *)
	ELSE (* delay to simulate random arrival time *)
		CASE type OF
			Wash		:	mean := WashArrivalMean |
			WashAndWax	:	mean 
							:= WashAndWaxArrivalMean;
		END; (* CASE *)
		Hold( ExponentialDistribution( mean ) );
	END; (* IF *)
	
	CreateNewCar( type ); (* create next car of this
								type *)
	
	LoggitCar( whoAmI,"Entering car wash entrance.",
				type );
	WITH carDesc DO
		Service := type;
		Car := whoAmI;
	END; (* WITH *)
	(* queue up for service *)
	PlaceOrder( CarWashEntrance, ADR(carDesc) ); 
	LoggitCar( whoAmI,"Leaving car wash.",type );
	
	HaltThread; (* service over,
					car disappears into sunset *)
END SimulateCar;

PROCEDURE CreateNewCar( type : DesiredService );
VAR
	cp	:	CarParams;
BEGIN
	IF CurrentTime() < timeLimit
	THEN
		INC(latestCar[type]);
		cp.WhoAmI := latestCar[type];
		cp.type := type;
		CreateNewThread( SimulateCar,ADR(cp),WS);
	ELSE
		HaltSimulation;
	END; (* IF *)
END CreateNewCar;

PROCEDURE InitSimulation;
VAR
	i	:	DesiredService; (* loop control *)
	n	:	CARDINAL; (* loop control *)
	wp	:	WasherParams;
	dur	:	CARDINAL;
	
BEGIN
	WritePString("How many washers are there?  ");
	ReadCard( numberOfWashers );
	WritePString(
	 "How long should the simulation run (minutes) ? ");
	ReadCard( dur );
	timeLimit := dur * 100; 
		(* Duration is in 0.01-minute units *)
	WriteString("****** Simulation run with ");
	WriteCard( numberOfWashers,3 );
	WriteString(" washers,");
	WriteLn;
	WriteString("****** for a duration of ");
	WriteCard( timeLimit,6 );
	WriteString(".");
	WriteLn;
	
	FOR i := MIN(DesiredService) 
		TO MAX(DesiredService) DO
		latestCar[i] := 0;
		END; (* FOR *)
	InitializeQueue( CarWashEntrance );
	
	(* Create all washers *)
	FOR n := 1 TO numberOfWashers DO
		wp.WhoAmI := n;
		CreateNewThread( SimulateWasher,ADR(wp),WS );
	END; (* FOR *)
	
	(* Create one of each kind of car *);
	CreateNewCar( Wash );
	CreateNewCar( WashAndWax );
	
END InitSimulation;

BEGIN
	InitSimulation;
	HaltThread;
END CarWash.